using System;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;

namespace SaturnExplorer
{
	public class WinInterop
	{
		private readonly Window _window;
		public WinInterop(Window window)
		{
			_window = window;
		}

		public static System.IntPtr WindowProc(
			  System.IntPtr hwnd,
			  int msg,
			  System.IntPtr wParam,
			  System.IntPtr lParam,
			  ref bool handled)
		{
			switch (msg)
			{
				case 0x0024:/* WM_GETMINMAXINFO */
					WmGetMinMaxInfo(hwnd, lParam);
					handled = true;
					break;
			}

			return (System.IntPtr)0;
		}

		public IntPtr DragHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handeled)
		{
			switch ((WM)msg)
			{
				case WM.WINDOWPOSCHANGING:
					{
						WINDOWPOS pos = (WINDOWPOS)Marshal.PtrToStructure(lParam, typeof(WINDOWPOS));
						if ((pos.flags & (int)SWP.NOMOVE) != 0)
						{
							return IntPtr.Zero;
						}

						Window wnd = (Window)HwndSource.FromHwnd(hwnd).RootVisual;
						if (wnd == null)
						{
							return IntPtr.Zero;
						}

						bool changedPos = false;

						// ***********************
						// Here you check the values inside the pos structure
						// if you want to override them just change the pos
						// structure and set changedPos to true
						// ***********************

						// this is a simplified version that doesn't work in high-dpi settings
						// pos.cx and pos.cy are in "device pixels" and MinWidth and MinHeight 
						// are in "WPF pixels" (WPF pixels are always 1/96 of an inch - if your
						// system is configured correctly).
						if (pos.cx < _window.MinWidth) { pos.cx = Convert.ToInt32(_window.MinWidth); changedPos = true; }
						if (pos.cy < _window.MinHeight) { pos.cy = Convert.ToInt32(_window.MinHeight); changedPos = true; }


						// ***********************
						// end of "logic"
						// ***********************

						if (!changedPos)
						{
							return IntPtr.Zero;
						}

						Marshal.StructureToPtr(pos, lParam, true);
						handeled = true;
					}
					break;
			}

			return IntPtr.Zero;
		}

		private static void WmGetMinMaxInfo(System.IntPtr hwnd, System.IntPtr lParam)
		{

			MINMAXINFO mmi = (MINMAXINFO)Marshal.PtrToStructure(lParam, typeof(MINMAXINFO));

			// Adjust the maximized size and position to fit the work area of the correct monitor
			int MONITOR_DEFAULTTONEAREST = 0x00000002;
			System.IntPtr monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);

			if (monitor != System.IntPtr.Zero)
			{

				MONITORINFO monitorInfo = new MONITORINFO();
				GetMonitorInfo(monitor, monitorInfo);
				RECT rcWorkArea = monitorInfo.rcWork;
				RECT rcMonitorArea = monitorInfo.rcMonitor;
				mmi.ptMaxPosition.x = Math.Abs(rcWorkArea.left - rcMonitorArea.left);
				mmi.ptMaxPosition.y = Math.Abs(rcWorkArea.top - rcMonitorArea.top);
				mmi.ptMaxSize.x = Math.Abs(rcWorkArea.right - rcWorkArea.left);
				mmi.ptMaxSize.y = Math.Abs(rcWorkArea.bottom - rcWorkArea.top);
			}

			Marshal.StructureToPtr(mmi, lParam, true);
		}

		internal enum WM
		{
			WINDOWPOSCHANGING = 0x0046,
		}

		internal enum SWP
		{
			NOMOVE = 0x0002,
		}

		[StructLayout(LayoutKind.Sequential)]
		internal struct WINDOWPOS
		{
			public IntPtr hwnd;
			public IntPtr hwndInsertAfter;
			public int x;
			public int y;
			public int cx;
			public int cy;
			public int flags;
		}

		/// <summary>
		/// POINT aka POINTAPI
		/// </summary>
		[StructLayout(LayoutKind.Sequential)]
		public struct POINT
		{
			/// <summary>
			/// x coordinate of point.
			/// </summary>
			public int x;
			/// <summary>
			/// y coordinate of point.
			/// </summary>
			public int y;

			/// <summary>
			/// Construct a point of coordinates (x,y).
			/// </summary>
			public POINT(int x, int y)
			{
				this.x = x;
				this.y = y;
			}
		}

		[StructLayout(LayoutKind.Sequential)]
		public struct MINMAXINFO
		{
			public POINT ptReserved;
			public POINT ptMaxSize;
			public POINT ptMaxPosition;
			public POINT ptMinTrackSize;
			public POINT ptMaxTrackSize;
		};


		/// <summary>
		/// </summary>
		[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
		public class MONITORINFO
		{
			/// <summary>
			/// </summary>            
			public int cbSize = Marshal.SizeOf(typeof(MONITORINFO));

			/// <summary>
			/// </summary>            
			public RECT rcMonitor = new RECT();

			/// <summary>
			/// </summary>            
			public RECT rcWork = new RECT();

			/// <summary>
			/// </summary>            
			public int dwFlags = 0;
		}


		/// <summary> Win32 </summary>
		[StructLayout(LayoutKind.Sequential, Pack = 0)]
		public struct RECT
		{
			/// <summary> Win32 </summary>
			public int left;
			/// <summary> Win32 </summary>
			public int top;
			/// <summary> Win32 </summary>
			public int right;
			/// <summary> Win32 </summary>
			public int bottom;

			/// <summary> Win32 </summary>
			public static readonly RECT Empty = new RECT();

			/// <summary> Win32 </summary>
			public int Width
			{
				get { return Math.Abs(right - left); }  // Abs needed for BIDI OS
			}
			/// <summary> Win32 </summary>
			public int Height
			{
				get { return bottom - top; }
			}

			/// <summary> Win32 </summary>
			public RECT(int left, int top, int right, int bottom)
			{
				this.left = left;
				this.top = top;
				this.right = right;
				this.bottom = bottom;
			}


			/// <summary> Win32 </summary>
			public RECT(RECT rcSrc)
			{
				this.left = rcSrc.left;
				this.top = rcSrc.top;
				this.right = rcSrc.right;
				this.bottom = rcSrc.bottom;
			}

			/// <summary> Win32 </summary>
			public bool IsEmpty
			{
				get
				{
					// BUGBUG : On Bidi OS (hebrew arabic) left > right
					return left >= right || top >= bottom;
				}
			}
			/// <summary> Return a user friendly representation of this struct </summary>
			public override string ToString()
			{
				if (this == RECT.Empty) { return "RECT {Empty}"; }
				return "RECT { left : " + left + " / top : " + top + " / right : " + right + " / bottom : " + bottom + " }";
			}

			/// <summary> Determine if 2 RECT are equal (deep compare) </summary>
			public override bool Equals(object obj)
			{
				if (!(obj is Rect)) { return false; }
				return (this == (RECT)obj);
			}

			/// <summary>Return the HashCode for this struct (not garanteed to be unique)</summary>
			public override int GetHashCode()
			{
				return left.GetHashCode() + top.GetHashCode() + right.GetHashCode() + bottom.GetHashCode();
			}


			/// <summary> Determine if 2 RECT are equal (deep compare)</summary>
			public static bool operator ==(RECT rect1, RECT rect2)
			{
				return (rect1.left == rect2.left && rect1.top == rect2.top && rect1.right == rect2.right && rect1.bottom == rect2.bottom);
			}

			/// <summary> Determine if 2 RECT are different(deep compare)</summary>
			public static bool operator !=(RECT rect1, RECT rect2)
			{
				return !(rect1 == rect2);
			}


		}

		[DllImport("user32")]
		internal static extern bool GetMonitorInfo(IntPtr hMonitor, MONITORINFO lpmi);

		/// <summary>
		/// 
		/// </summary>
		[DllImport("User32")]
		internal static extern IntPtr MonitorFromWindow(IntPtr handle, int flags);
	}
}